//
// Created by Key-Box-Min on 29/06/2020.
//

#ifndef COMPILERDOMEA1_THREADCONTAINER_H
#define COMPILERDOMEA1_THREADCONTAINER_H

#include <thread>
#include <atomic>
#include <mutex>

#include <functional>
#include <tuple>

#include <list>
#include <set>
#include <vector>
#include <map>
#include <deque>

template <typename T,typename _Compare  = std::less<T>>
class CPriority_Queue : public std::deque<T>{
public:

    void push(const T& data){
        if(this->empty()){
            this->push_back(data);
        }else{
            auto p = this->begin();
            for(;p != this->end();p++)
                if(__compare(*p,data))break;
            if(p == this->end())this->push_back(data);
            else this->insert(p,data);
        }
    }

private:
    _Compare        __compare;
};

template <typename T>
class TMutex : public T {
protected:
    struct Lock{
    public:
        Lock(TMutex* const & data,std::mutex& mutex):__data(data),__lock(mutex){}
        T* operator->(){return (T*)__data;}
    private:
        TMutex* const&  __data;
        std::lock_guard<std::mutex> __lock;
    };

public:
    using T::T;
    Lock lock(){return Lock(this,this->__mutex);}
    std::lock_guard<std::mutex> lock_guard(){return std::lock_guard<std::mutex>(__mutex);}
    std::mutex& mutex(){return __mutex;}


private:
    std::mutex   __mutex;
};

template <typename T,typename TC>
class TContainer : public TMutex<TC>{
public:
    bool ergodic_at(const std::size_t& index,T& data){
        auto l = this->lock_guard();
        if(index >= this->size())return false;
        auto p = this->begin();
        std::advance(p,index);
        data = *p;
        return true;
    }

    bool getOne(T& data){
        auto l = this->lock_guard();
        if(this->empty())return false;
        data = *this->begin();
        this->erase(this->begin());
        return true;
    }

    std::vector<T> getAll(const std::size_t& count){
        auto l = this->lock_guard();
        std::vector<T> rul;
        std::size_t _count = (this->size() > count)? count : this->size();
        if(_count == 0)return rul;
        rul.reserve(_count);
        auto p = this->begin();
        for(std::size_t i = 0;i<_count;i++,p++)rul.push_back(*p);
        this->erase(this->begin(),p);
        return rul;
    }
};

template <typename T>
using TSet = TContainer<T,std::set<T>>;

template <typename K,typename T>
using TMap = TContainer<std::pair<K,T>,std::map<K,T>>;

template <typename T>
using TList = TContainer<T,std::list<T>>;


template <typename T>
class ThreadContainer{
public:
    ThreadContainer(){
        __queue_pop_p = &__queue_a;
        __queue_push_p = &__queue_b;
    }

    void push(const T& data){
        std::lock_guard<std::mutex> l(__push_mutex);
        __queue_push_p->push_front(data);
    }

    bool pop(T& data){
        exchange();
        std::lock_guard<std::mutex> l(__pop_mutex);
        if(__queue_pop_p->empty())return false;
        data = __queue_pop_p->back();
        __queue_pop_p->pop_back();
        return true;
    }
    bool pop_peek(T& data){
        exchange();
        std::lock_guard<std::mutex> l(__pop_mutex);
        if(__queue_pop_p->empty())return false;
        data = __queue_pop_p->back();
        return true;
    }

    void erase(){

    }

    int size(){
        std::lock(__pop_mutex,__push_mutex);
        std::lock_guard<std::mutex> l1(__pop_mutex,std::adopt_lock);
        std::lock_guard<std::mutex> l2(__push_mutex,std::adopt_lock);
        return __queue_pop_p->size() + __queue_push_p->size();
    }

    bool empty(){
        std::lock(__pop_mutex,__push_mutex);
        std::lock_guard<std::mutex> l1(__pop_mutex,std::adopt_lock);
        std::lock_guard<std::mutex> l2(__push_mutex,std::adopt_lock);
        return __queue_push_p->empty() && __queue_pop_p->empty();
    }

protected:
    void exchange(){
        __pop_mutex.lock();
        if(__queue_pop_p->empty()){
            __pop_mutex.unlock();

            std::lock(__pop_mutex,__push_mutex);
            std::lock_guard<std::mutex> l1(__pop_mutex,std::adopt_lock);
            std::lock_guard<std::mutex> l2(__push_mutex,std::adopt_lock);
            if(!__queue_pop_p->empty())return;

            std::list<T>*p = __queue_pop_p;
            __queue_pop_p = __queue_push_p;
            __queue_push_p = p;
        }else{
            __pop_mutex.unlock();
        }
    }
private:
    std::list<T> __queue_a;
    std::list<T> __queue_b;
    std::list<T>* __queue_push_p;
    std::list<T>* __queue_pop_p;

    std::mutex  __pop_mutex;
    std::mutex  __push_mutex;
};

#endif //COMPILERDO